/************************************************************************************
 * Nombre del archivo: Shell.java
 * Universidad del Valle de Guatemala
 * Curso: Sistemas Operativos
 * @author martin
 *          Martín Guzmán 08041
 * Fecha: Febrero de 2010
 * Descripción: Contiene el shell del proyecto. En la línea de comando
 *              se mandan a llamar a los system programs y a los user
 *              programs
*************************************************************************************/

import java.io.File;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.io.BufferedReader;
import java.io.File;
import java.io.*;
import java.util.Hashtable;
import java.util.LinkedList;


public class Shell extends Thread{


    private Scanner scan = new Scanner(System.in);
    public volatile static File Directorio_Raiz = new File("/home/martin/Documents/UVG/Sistemas Operativos/Proyectos/Proyecto No.2/Ejemplos");

    public volatile static String path_io_log = "/home/martin/Desktop/io.log"; //roots de los logs
    public volatile static String path_ps_log = "/home/martin/Desktop/ps.log"; //roots de los logs

    private static LinkedList<String> lista_instrucciones = new LinkedList<String>();
    private static boolean sincrono=true;
    private static File outputFile;
    private static FileWriter out;

    public static boolean debug_mode=false;

    public static boolean sincrono_debug=true;
    public static String debug_path="";
    private static File outputFile_debug;
    private static FileWriter out_debug;


    public Shell(){
        System.out.println("   _________               ______                   ___     ______               _____       ____   \n"
                            +"   |''''[]'|=======[]    .' ___  |                .'   `. .' ____ \\             / ___ `.   .'    '.    \n"
                            +"   |________\\____       / .'   \\_| __   _   ____ /  .-.  \\| (___ \\_|  _   __   |_/___) |  |  .--.  |  \n"
                            +"   |==+==========\\      | |   ____[  | | | [_   ]| |   | | _.____`.  [ \\ [  ]   .'____.'  | |    | |   \n"
                            +"   |______________|     \\ `.___]  || \\_/ |, .' /_\\  `-'  /| \\____) |  \\ \\/ /_  / /_____  _|  `--'  |  \n"
                            +"   \\(@))(@)(@)(@)/       `._____.' '.__.'_/[_____]`.___.'  \\______.'   \\__/(_) |_______|(_)'.____.' \n"
                            +"##########################################################################################################");


        System.out.println("\nWelcome to GuzOS v.0.1 <http://code.google.com/p/guz08041/>");
        System.out.println("Type help and hit Enter for context help. Or type exit to quit.\n");
        
        //System_program system_program = new System_program();
    }

    /***********************************************
     * Método: run
     * Uso: Método que ejecuta el shell mientras está
     *      encendido
     *@return void
     ***********************************************/
    public void run() {

        String instruccion_actual="";
        do{
            //se queda en loopado hasta que se acabe el proceso síncrono... ****
            try{
                //TODO arreglar esto...
                while(CPU.contiene_proceso_sincrono()){
                    try {

                        this.sleep(10);
                    } catch (Exception ex) {
                        System.out.println("Exception en sleep");
                    }
                }
            }catch(Exception exp){}
            //******************************************************************

            instruccion_actual = Prompt();
            String[] inst = instruccion_actual.split("-");

            for(int i=0;i<inst.length;i++){
                instruccion_actual = this.Quitar_espacios(inst[i]);
                //System.out.println(" ---------- instrucción -----------------"+instruccion_actual+".");
                //while(CPU.contiene_proceso_sincrono()){}

                //revisa la salida del programa
                if(instruccion_actual.compareToIgnoreCase("exit")==0){
                    break;
                }
                /*
                if(instruccion_actual.compareToIgnoreCase("restart")==0){
                    Main.restart=true;
                    break;
                }
                 */
                if(instruccion_actual.compareToIgnoreCase("help")==0){
                    Help();
                }
                else if(revisar_system_program(instruccion_actual)){}
                else{
                    if(instruccion_actual!="")
                        System.out.println(instruccion_actual+": command not found");
                }
                //*******************************************************

            }
            if(instruccion_actual.compareToIgnoreCase("exit")==0){
                break;
            }
            if(instruccion_actual.compareToIgnoreCase("restart")==0){
                break;
            }
        }while(true);
    }


    /***********************************************
     * Método: Prompt
     * Uso: Despliega el prompt para que ingrese
     * las instrucciones a ejecutar.
     *@return instrucción ingresada por el usuario.
     ***********************************************/
    public String Prompt(){
        System.out.print(this.Directorio_Raiz.getPath()+": ");
        return Quitar_espacios(scan.nextLine());
    }

    /***********************************************
     * Método: comparar_cadenas
     * Uso: compara 2 cadenas
     * @param cadena1 a comparar
     * @param cadena2 para comparar con cadena1
     * @return booleano que dice si son iguales las cadenas
    ***********************************************/
    private static boolean comparar_cadenas(String cadena1, String cadena2) {

        if(cadena1.compareTo(cadena2)==0){
            return true;
        }
        return false;
    }

    /***********************************************
     * Método: Quitar espacios
     * Uso: quita espacios inservibles
     * @return cadena sin espacios inservibles
     *          y separando los átomos por
     *         espacios
    ***********************************************/
    public String Quitar_espacios(String instruccion){
        String retorno=instruccion;

        while(instruccion.contains("\t"))
            retorno=retorno.replace("\t"," ");

        while(retorno.contains("  ")){
            retorno=retorno.replaceAll("  "," ");
        }

        //por si inicia con un espacio
        if(retorno.startsWith(" ")){
            retorno = retorno.substring(1);
        }

        //quitar el espacio al final
        if(retorno.endsWith(" ")){
            retorno = retorno.substring(0, retorno.length()-1);
        }

        if(retorno.length()==0){
            return "";
        }

        return retorno;
    }

    /*****************************************
     * Método: setDirectorio_Raiz
     * Uso: cambia el directorio raiz
     * @param path un arhivo que contiene el path que se desea para directorio raiz
     * @return instrucciones para usos.
     *****************************************/
    public static void setDirectorio_Raiz(String path){
        Directorio_Raiz = new File(path);
    }

    /*****************************************
     * Método: Help
     * Uso: Ayuda para el usuario.
     * @return instrucciones para usos.
     *****************************************/
    public void Help(){
        System.out.print("helping..!");//TODO arreglar la ayuda
    }

    /***********************************************
     * Método: revisar_system_program
     * Uso: revisa si hay alguna acción que tenga
     *      que hacer el system program
     * @param cadena que contiene la instrucción a revisar
     * @return booleano que dice si se realizó una acción
    ***********************************************/
    public static boolean revisar_system_program(String cadena){
        //****************************************************** ls ******************************************************
        if(comparar_cadenas(cadena.split(" ")[0],"ls")){
            if(cadena.split(" ").length==1 && !cadena.contains(">")){
                ls();
            }
            else if(cadena.split(" ").length==3 && cadena.contains(">")){
                sincrono=false;
                outputFile = new File(Directorio_Raiz.getPath()+"/"+cadena.split(" ")[2]);
                try {
                    out = new FileWriter(outputFile);
                    ls();
                } catch (Exception ex) {
                    System.out.println("error al abrir el archivo 5");
                    return false;
                }
            }
            else{
                return false;
            }
            return true;
        }
        //****************************************************** kill ******************************************************
        else if(comparar_cadenas(cadena.split(" ")[0],"kill")){

            if(cadena.split(" ").length==2 && !cadena.contains(">")){
                kill(cadena.split(" ")[1]);
            }
            else if(cadena.split(" ").length==4 && cadena.contains(">")){
                sincrono=false;

                outputFile = new File(Directorio_Raiz.getPath()+"/"+cadena.split(" ")[3]);
                try {
                    out = new FileWriter(outputFile);
                    kill(cadena.split(" ")[1]);
                } catch (Exception ex) {
                    System.out.println("error al abrir el archivo");
                    return false;
                }
            }
            else{
                return false;
            }
            
            return true;
        }
        //****************************************************** ps ******************************************************
        else if(comparar_cadenas(cadena.split(" ")[0],"ps")){
            if(cadena.split(" ").length==1 && !cadena.contains(">")){
                ps();
            }
            else if(cadena.split(" ").length==3 && cadena.contains(">")){
                sincrono=false;
                outputFile = new File(Directorio_Raiz.getPath()+"/"+cadena.split(" ")[2]);
                try {
                    out = new FileWriter(outputFile);
                    ps();
                } catch (Exception ex) {
                    System.out.println("error al abrir el archivo");
                    return false;
                }
            }
            else{
                return false;
            }
            
            return true;
        }
        //****************************************************** debug_mode ******************************************************
        else if(comparar_cadenas(cadena.split(" ")[0],"debug_mode")){

            if(cadena.split(" ").length==2 && !cadena.contains(">")){
                debug_mode(cadena);
            }
            else if(cadena.split(" ").length==4 && cadena.contains(">")){
                sincrono_debug=false;

                debug_path=(Directorio_Raiz.getPath()+"/"+cadena.split(" ")[3]);
                debug_mode(cadena);
            }
            else{
                return false;
            }

            return true;
        }
        //****************************************************** rm_logs ******************************************************
        else if(comparar_cadenas(cadena,"rm_logs")){
            //System.out.println("remove logs");
            rm_logs();
            return true;
        }
        //****************************************************** rm ******************************************************
        else if(comparar_cadenas(cadena.split(" ")[0],"rm")){
            if(cadena.split(" ").length==2){
                rm(cadena.split(" ")[1]);
            }

            return true;
        }
        else if(se_encuentra_en_directorio_raiz_y_es_archivo(cadena)){
            Crear_Proceso(cadena);
            return true;
        }
        return false;
    }

    /***********************************************
     * Método: se_encuentra_en_directorio_raiz_y_es_archivo
     * Uso: revisa si se quiere ejecutar un user program
     * @param cadena que contiene la instrucción a revisar
     * @return booleano que dice si lo pedido es un user program
    ***********************************************/
    public static boolean se_encuentra_en_directorio_raiz_y_es_archivo(String cadena){

        String cadena1 = cadena.split(" ")[0];

        File[] file2 = Directorio_Raiz.listFiles();

        //ciclo lo tecleado está en el directorio raiz
        for(int i=0;i<file2.length;i++){
            //revisa si los nombres son iguales y si el file que se está revisando no es carpeta
            if(comparar_cadenas(file2[i].getName(),cadena1) && file2[i].isFile()){
                return true;
            }
        }
        return false;
    }

    /***********************************************
     * Método: ls
     * Uso: imprime en pantalla todos los archivos del
     *      directorio raiz
     * @return void imprime directo en pantalla
     *      los archivos del directorio raiz
    ***********************************************/
    private static void ls() {

        File[] file2 = Directorio_Raiz.listFiles();
        System.out.println();
        String resultado = "\n";
        //ciclo que imprime cada archivo
        for(int i=0;i<file2.length-1;i++){
            resultado+=(file2[i].getName()+"\n");
        }
        resultado+="\n";
        Imprimir(resultado);
    }
    
    /***********************************************
     * Método: kill
     * Uso: Termina el proceso con pid indicado.
     * @return void
    ***********************************************/
    private static void kill(String valor) {
        //TODO verificar el kill...
        int PID_a_matar=-1;
        try{
            PID_a_matar = Integer.parseInt(valor);

            int j=-1;
            LinkedList<Proceso> temp;
            //revisar en cada cola...
            for(int a=0;a<CPU.lista_colas.size();a++){

                for(int i=0;i<CPU.lista_colas.get(a).getCola().size();i++){

                    if(CPU.lista_colas.get(a).getCola().get(i).getPID()==PID_a_matar){
                        CPU.Procesos_por_matar.add(CPU.lista_colas.get(a).getCola().get(i));
                        break;
                    }
                }

            }

        }catch(Exception exp){
            System.out.println("Parámetros de kill erróneos");
        }
        System.out.println("killing process! :P!");

    }
    /***********************************************
     * Método: ps
     * Uso: Muestra todos los procesos activos dentro
     *      del S.O. Además, debe mostrar los archivos
     *      que cada proceso tiene en uso.
     * @return void
    ***********************************************/
    private static void ps() {

        String resultado="";

        //resultado+=("Todos los procesos\n");
        LinkedList<Proceso> temp3 = CPU.Todos_Procesos;//obtiene la lista
        if(temp3.isEmpty()){
            resultado+=("Aún no hay procesos en cola\n");
        }
        for(int i=0;i<temp3.size();i++){
            resultado+=("Process: "+temp3.get(i).getPID()+", State: "+temp3.get(i).get_Nombre_Estado()+" ("+temp3.get(i).getEstado()+")")+"\n";
            resultado+="\t"+("Proceso padre: "+temp3.get(i).getPadre()+"\n");
            resultado+="\t"+("Prioridad: "+temp3.get(i).getPrioridad()+"\n");
            resultado+="\t"+("Se encuentra en cola: "+(temp3.get(i).cola_en_la_que_esta+1)+"\n");
            String temporal = "";
            
            LinkedList<Archivo> cola = CPU.Archivos_PID;
            for(int j=0;j<cola.size();j++){
                if(temp3.get(i).getPID()==cola.get(j).getPID_de_controlador()){
                    temporal+=cola.get(i).getPath();
                }
            }
            resultado+="\t"+("Archivos en uso: "+((temporal.compareTo("")==0)?"Ninguno":temporal)+"\n");
            
        }
        Imprimir(resultado);
    }
    /***********************************************
     * Método: debug_mode
     * Uso: para activar el debug de un proceso
     * @return void
    ***********************************************/
    private static void debug_mode(String string) {
        String[] temp = string.split(" ");
        if(comparar_cadenas(temp[1],"true")){
            debug_mode = true;
            System.out.println("Debug mode: true");
        }
        else if(comparar_cadenas(temp[1],"false")){
            debug_mode = false;
            System.out.println("Debug mode: false");
        }
        else{
            System.out.println("Parámetro del debug incorrecto");
        }
    }
    /***********************************************
     * Método: rm_logs
     * Uso: borrar los logs de io.log y ps.log
     * @return void
    ***********************************************/
    private static void rm_logs() {
        File fichero = new File(path_io_log);
        File fichero2 = new File(path_ps_log);
        fichero.delete();
        fichero2.delete();

    }
    /***********************************************
     * Método: rm
     * Uso: borrar los logs de io.log y ps.log
     * @return void
    ***********************************************/
     private static void rm(String string) {
        String destino = Directorio_Raiz.getPath()+"/"+ string;
        //System.out.println(destino);
        File fichero = new File(destino);
        fichero.delete();
    }
    /***********************************************
     * Método: Crear_Proceso
     * Uso: crea un proceso con la cadena
     * @param cadena que contiene el proceso que se desea
     * @return void
    ***********************************************/
    public static void Crear_Proceso(String cadena) {

        String[] cadena_separada = cadena.split(" ");
        String nombre_archivo_destino ="";
        boolean sincrono=true;
        boolean temp_correcto = true;

        int prioridad = 4;// prioridad default

        //revisar si se le está asignando prioridad
        if(cadena_separada[cadena_separada.length-1].startsWith("p") || cadena_separada[cadena_separada.length-1].startsWith("P")){
            try{
                int num = Integer.parseInt(cadena_separada[cadena_separada.length-1].substring(1));
                if(num<=9 && num>=0){
                    prioridad = num; //asigna prioridad
                }
            }catch(Exception exp){Imprimir("valor no numérico como prioridad");temp_correcto=false;}
            cadena = cadena.substring(0, cadena.lastIndexOf(" "));
            cadena_separada = cadena.split(" ");
        }
        //System.out.println("."+cadena+".");//TODO quitar esto
        //System.out.println("."+prioridad+".");//TODO quitar esto

        try{
            if(cadena.contains(">")){
                if(comparar_cadenas(cadena_separada[cadena_separada.length-2],">")){
                    sincrono = false;
                    nombre_archivo_destino = cadena_separada[cadena_separada.length-1];
                    cadena = cadena.substring(0, cadena.lastIndexOf(">")-1);
                    cadena_separada = cadena.split(" ");
                }
            }
        }catch(Exception exp){}
        //System.out.println("."+cadena+".");//TODO quitar esto
        
        if((cadena_separada.length<=9 && cadena_separada.length>0)){

            for(int i=1;i<cadena_separada.length;i++){
                try{
                    Integer.parseInt(cadena_separada[i]);
                }catch(Exception exp){temp_correcto=false; System.out.println("parámetros incorrectos"); break;}

            }

            if(temp_correcto){
                    File archivo_ejecutar = new File(Directorio_Raiz.getPath()+"/"+cadena_separada[0]);
                try {
                    User_program(cadena_separada, archivo_ejecutar, sincrono, nombre_archivo_destino,prioridad);
                } catch (Exception ex) {
                    System.out.println("error al abrir el archivo: "+archivo_ejecutar);
                }
            }
            
        }
        else{
            System.out.println("Cantidad de parámetros incorrecta...");
        }
    }
    /***********************************************
     * Método: User_program
     * Uso: constructor
     * @return void
    ***********************************************/
    public static void User_program(String[] cadena_separada, File archivo, boolean Sincrono, String nombre_archivo_destino,int prioridad) {

        Proceso temp_proceso;
        if(leer_archivo(archivo.getPath())){
            if(Sincrono){
                //modo síncrono
                
                temp_proceso = new Proceso(lista_instrucciones,cadena_separada, archivo,prioridad);
            }
            else{
                //modo asíncrono
                temp_proceso = new Proceso(lista_instrucciones,cadena_separada, archivo, nombre_archivo_destino,prioridad);
            }
            
            //para revisar si agregar a la pila o no está correcta la inicialización del proceso
            if(temp_proceso.proceso_correcto()){
                //procede a agregar a la cola
                //System.out.println("si entra");
                CPU.Agregar_Proceso(temp_proceso);
            }
            else{
                System.out.println("hubo un error en la inicialización del proceso");
            }
        }
    }
    /***********************************************
     * Método: leer_archivo
     * Uso: lee un archivo indicado por el path
     * @param path del archivo a leer
     * @return booleano diciendo si se leyó bien el archivo
    ***********************************************/
    private static boolean leer_archivo(String path) {
        String sCadena ="", archivo="";
        
        //borrar la lista existente
        lista_instrucciones.clear();

        try{
            FileReader fr = new FileReader(path);
            BufferedReader bf = new BufferedReader(fr);
            while ((sCadena = bf.readLine())!=null) {
                lista_instrucciones.add(sCadena);
            }
            bf.close();
            fr.close();
        }catch(Exception exception){
            System.out.println("error en el archivo");
            return false;
        }
        return true;
    }
    /***********************************************
     * Método: Imprimir
     * Uso: decide si escribir en un archivo o en la terminal
     *      dependiendo de si es síncrono o no
     * @param imprimir contiene lo que se desea imprimir
     * @return void
    ***********************************************/
    private static void Imprimir(String imprimir) {

        //si es síncrona se imprime en la terminal
        if(sincrono){
            System.out.print(imprimir);
        }
        //si no es síncrona se imprime en el archivo
        else{
            escribir_archivo(imprimir);
        }
    }
    /***********************************************
     * Método: escribir_archivo
     * Uso: escribe en un archivo la cadena a imprimir
     *      en un archivo descrito por el path
     * @param path del archivo a escribir
     * @param imprimir. El contenido que se imprime en el archivo
     * @return boolean diciendo si se escribió el archivo correctamente
    ***********************************************/
    private static boolean escribir_archivo(String imprimir) {

        try{
            //System.out.println("imprimir: "+imprimir);
            out.write(imprimir);
            out.close();
            sincrono=true;

        }catch(Exception exp){
            System.out.println("Error al guardar archivo\n");
            return false;
        }
        return true;

    }
    /***********************************************
     * Método: escribir_io_log
     * Uso: escribe el log con las peteciones de abrir y cerrar archivos
     * @param imprimir contiene lo que se desa imprimir
     * @return boolean diciendo si se guardó el archivo correctamente
    ***********************************************/
    private static boolean escribir_io_log(String imprimir) {

        File TextFile = new File(Shell.path_io_log);
        FileWriter TextOut;
        try {
            TextOut = new FileWriter(TextFile, true);
            TextOut.write(imprimir+"\n");
            TextOut.close();
        } catch (IOException ex) {
            System.out.println("error al guardar el log de io");
            return false;
        }
        return true;

    }
    /***********************************************
     * Método: escribir_io_log
     * Uso: escribe el log con las peteciones de abrir y cerrar archivos
     * @param imprimir contiene lo que se desa imprimir
     * @return boolean diciendo si se guardó el archivo correctamente
    ***********************************************/
    private static boolean escribir_ps_log(String imprimir) {

        File TextFile = new File(Shell.path_ps_log);
        FileWriter TextOut;
        try {
            TextOut = new FileWriter(TextFile, true);
            TextOut.write(imprimir+"\n");
            TextOut.close();
        } catch (IOException ex) {
            System.out.println("error al guardar el log de ps");
            return false;
        }
        return true;

    }

}
